﻿unit AuthMiddleware;

interface

uses
  System.SysUtils, System.Generics.Collections, System.DateUtils, System.Classes,
  Sparkle.HttpServer.Module, Sparkle.HttpServer.Context, Sparkle.Security,
  BasicUserIdentity;  // 添加 BasicUserIdentity 单元

type
  TTokenInfo = record
    Token: string;
    UserId: string;
    UserName: string;
    Roles: TArray<string>;
    Expires: TDateTime;
    function IsValid: Boolean;
    function HasRole(const Role: string): Boolean;
  end;

  IAuthenticationService = interface
    ['{D1E8F4A2-3B9C-4F7A-8D2E-9F6A5B3C7D8E}']
    function Authenticate(const Username, Password: string): TTokenInfo;
    function ValidateToken(const Token: string): TTokenInfo;
    procedure InvalidateToken(const Token: string);
  end;

  TAuthenticationMiddleware = class(THttpServerMiddleware)
  private
    FAuthService: IAuthenticationService;
    FExcludedPaths: TArray<string>;
  protected
    procedure ProcessRequest(Context: THttpServerContext; Next: THttpServerProc); override;
    function IsPathExcluded(const Path: string): Boolean;
  public
    constructor Create(AuthService: IAuthenticationService; const ExcludedPaths: TArray<string> = []);
  end;

  TSimpleAuthenticationService = class(TInterfacedObject, IAuthenticationService)
  private
    FTokens: TDictionary<string, TTokenInfo>;
    FSecretKey: string;
    function GenerateToken(const UserId, UserName: string; const Roles: TArray<string>): string;
  public
    constructor Create(const SecretKey: string);
    destructor Destroy; override;

    function Authenticate(const Username, Password: string): TTokenInfo;
    function ValidateToken(const Token: string): TTokenInfo;
    procedure InvalidateToken(const Token: string);
  end;

implementation

uses
  System.NetEncoding;

{ TTokenInfo }

function TTokenInfo.IsValid: Boolean;
begin
  Result := (Token <> '') and (Expires > Now);
end;

function TTokenInfo.HasRole(const Role: string): Boolean;
var
  UserRole: string;
begin
  Result := False;
  for UserRole in Roles do
    if SameText(UserRole, Role) then
      Exit(True);
end;

{ TAuthenticationMiddleware }

constructor TAuthenticationMiddleware.Create(AuthService: IAuthenticationService;
  const ExcludedPaths: TArray<string>);
begin
  inherited Create;
  FAuthService := AuthService;
  FExcludedPaths := ExcludedPaths;
end;

function TAuthenticationMiddleware.IsPathExcluded(const Path: string): Boolean;
var
  ExcludedPath: string;
begin
  Result := False;
  for ExcludedPath in FExcludedPaths do
    if Path.StartsWith(ExcludedPath) then
      Exit(True);
end;

procedure TAuthenticationMiddleware.ProcessRequest(Context: THttpServerContext;
  Next: THttpServerProc);
var
  Token: string;
  TokenInfo: TTokenInfo;
  AuthHeader: string;
begin
  // 检查是否在排除路径中
  if IsPathExcluded(Context.Request.Uri.Path) then
  begin
    Next(Context);
    Exit;
  end;

  // 从Header中提取Token
  AuthHeader := Context.Request.Headers.Get('Authorization');
  if AuthHeader.StartsWith('Bearer ', True) then
    Token := AuthHeader.Substring(7).Trim
  else
    Token := '';

  if Token = '' then
    Token := Context.Request.Headers.Get('X-Auth-Token');

  // 验证Token
  if Token <> '' then
    TokenInfo := FAuthService.ValidateToken(Token)
  else
    TokenInfo := Default(TTokenInfo);

  if TokenInfo.IsValid then
  begin
    // Token有效，设置用户身份 - 修复这里
    Context.Request.User := TBasicUserIdentity.Create(
      TokenInfo.UserId,
      TokenInfo.UserName,
      TokenInfo.Roles
    );
    Next(Context);
  end
  else
  begin
    // Token无效，返回401
    Context.Response.StatusCode := 401;
    Context.Response.ContentType := 'application/json';
    Context.Response.Close(TEncoding.UTF8.GetBytes('{"error": "Unauthorized", "message": "Invalid or expired token"}'));
  end;
end;

{ TSimpleAuthenticationService }

constructor TSimpleAuthenticationService.Create(const SecretKey: string);
begin
  inherited Create;
  FTokens := TDictionary<string, TTokenInfo>.Create;
  FSecretKey := SecretKey;
end;

destructor TSimpleAuthenticationService.Destroy;
begin
  FTokens.Free;
  inherited;
end;

function TSimpleAuthenticationService.GenerateToken(const UserId, UserName: string;
  const Roles: TArray<string>): string;
begin
  // 简单实现，实际项目中应该使用JWT等标准
  Result := TGUID.NewGuid.ToString;
end;

function TSimpleAuthenticationService.Authenticate(const Username,
  Password: string): TTokenInfo;
begin
  // 这里实现实际的用户认证逻辑
  // 简化实现，实际项目中应该查询数据库
  if (Username = 'admin') and (Password = 'password') then
  begin
    Result.Token := GenerateToken('1', 'admin', ['admin', 'user']);
    Result.UserId := '1';
    Result.UserName := 'admin';
    Result.Roles := ['admin', 'user'];
    Result.Expires := IncHour(Now, 24);

    FTokens.AddOrSetValue(Result.Token, Result);
  end
  else
    Result := Default(TTokenInfo);
end;

function TSimpleAuthenticationService.ValidateToken(const Token: string): TTokenInfo;
begin
  if not FTokens.TryGetValue(Token, Result) then
    Result := Default(TTokenInfo)
  else if Result.Expires <= Now then
  begin
    FTokens.Remove(Token);
    Result := Default(TTokenInfo);
  end;
end;

procedure TSimpleAuthenticationService.InvalidateToken(const Token: string);
begin
  FTokens.Remove(Token);
end;

end.
